본문으로 건너뛰기

4장: 서버 사이드 렌더링

1. 서버 사이드 렌더링이란?

1. 싱글 페이지 애플리케이션의 세상

서버에서 모든 데이터를 불러오고 history.pushStatehistory.replaceState를 사용해 페이지를 작업한다. 서버의 부담을 사용자가 나눠 가지기 때문에 속도가 빠르지만 초기 로딩에 시간이 걸린다.

2. 서버 사이드 렌더링이란?

클라이언트 요청시 서버에서 HTML 파일을 미리 렌더링한 후 전달하는 방식이다.

1. 장점

  • 최초 페이지 진입이 빠르다.
  • 검색 엔진과 SNS 공유 등 메타데이터 제공이 쉽다.
  • 누적 레이아웃 이동이 적다.
  • 사용자 디바이스 성능에 비교적 자유롭다.
  • 보안에 좀 더 안전하다.

2. 단점

  • 소스코드를 작성할 때 항상 서버를 고려해야 한다.
  • 적절한 서버가 구축돼 있어야 한다.
  • 서버에서 렌더링을 완료해야 화면을 출력하기 때문에 서비스가 지연될 수 있다.

3. SPA와 SSR을 모두 알아야 하는 이유

1. 서버 사이드 렌더링 역시 만능이 아니다.

우선순위와 목적에 따라 효율적인 사용법을 익힐 필요가 있다.

2. 싱글 페이지 애플리케이션과 서버 사이드 렌더링 애플리케이션

  • 가장 뛰어난 싱글 페이지 애플리케이션은 가장 뛰어난 멀티 페이지 애플리케이션보다 낫다.
  • 평균적인 싱글 페이지 애플리케이션은 평균적인 멀티 페이지 애플리케이션보다 느리다.

3. 현대의 서버 사이드 렌더링

서버 사이드 렌더링 프레임워크를 사용해 싱글 페이지 애플리케이션과 유사한 사용자 경험을 제공한다.

2. 서버 사이드 렌더링을 위한 리액트 API 살펴보기

1. renderToString

리액트 컴포넌트를 HTML 문자열로 변환하여 서버 측 렌더링을 가능하게 만든다.

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import MyComponent from './MyComponent';

const html = ReactDOMServer.renderToString(<MyComponent />);

훅과 이벤트 함수를 클라이언트에서 처리하는 형식으로 사용된다.

2. renderToStaticMarkup

리액트 컴포넌트를 HTML 문자열로 변환하여 서버 측 렌더링을 가능하게 만든다. 리액트 기능을 포함하지 않은 순수 HTML 형식으로 변환해 정적인 페이지를 만든다. 블로그 글이나 상품의 약관 등의 페이지를 제작할 때 사용된다.

3. renderToNodeStream

HTML을 조각 형식으로 서버에 보내 초기 속도를 높인다.

4. renderToStaticNodeStream

renderToNodeStream 사용시 순수 HTML을 반환해야할 때 사용된다.

5. hydrate

렌더링 된 HTML 결과물에 자바스크립트 헨들러나 이벤트를 붙이는 역할을 수행한다.

3. Next.js 톺아보기

2. Next.js 시작하기

next.config.js, pages/_app.tsx, pages/_document.tsx, pages/_error.tsx, pages/404.tsx, pages/500.tsx, pages/index.tsx 등을 조작해 보여주고 싶은 페이지를 정의할 수 있다.

3. Data Fetching

1. getStaticProps

빌드 시에 데이터를 가져와 정적인 페이지를 만든다.

// pages/index.tsx
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();

return {
props: {
data,
},
};
}

function HomePage({ data }) {
return (
<div>
<h1>Static Site Generation Example</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}

export default HomePage;

2. getServerSideProps

요청 마다 페이지를 서버에서 렌더링 한다. 실시간 데이터를 가져오는 데 사용된다.

// pages/index.tsx
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();

return {
props: {
data,
},
};
}

function HomePage({ data }) {
return (
<div>
<h1>Server Side Rendering Example</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}

export default HomePage;

3. 클라이언트 사이드

동적으로 데이터를 받아오는 가장 많이 사용되는 방법이다.

import { useEffect, useState } from 'react';

function HomePage() {
const [data, setData] = useState(null);

useEffect(() => {
const fetchData = async () => {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
setData(data);
};
fetchData();
}, []);

return (
<div>
<h1>Client-Side Data Fetching Example</h1>
{data ? <pre>{JSON.stringify(data, null, 2)}</pre> : <p>Loading...</p>}
</div>
);
}

export default HomePage;

4. 스타일 적용하기

1. 전역 스타일

애플리케이션 전체에 공통으로 적용하고 싶은 스타일이 있다면 _app.tsx에 필요한 스타일을 직접 import로 불러오면 애플리케이션 전체에 영향을 미칠 수 있다

import type { AppProps } from 'next/app'
// 적용하고 싶은 글로벌 스타일
import '../styles.css'
// 혹은 node_modules에서 바로 꺼내올 수도 있다.
import 'normalize.css/normalize.css'
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

2. 컴포넌트 레벨 CSS

[name].module.css 와 같은 명명 규칙만 준수하면 되며, 이 컴포넌트 레벨 CSS는 다른 컴포넌트의 클래스명과 겹쳐서 스타일에 충돌이 일어나지 않도록 고유한 클래스명을 제공한다

.button {
background-color: #0070f3;
color: white;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}

.button:hover {
background-color: #005bb5;
}
import styles from './Button.module.css'; // CSS 모듈 임포트

const Button = () => {
return (
<button className={styles.button}>Click Me</button>
);
};

export default Button;

작성하고 느낀 점

좋았던 점: 과거 SSR의 단점을 보완하기 위한 SPA의 단점을 보완하기 위한 현대 SSR 까지의 발전 과정이 재미있었다.

배운 점: 서버 사이드 렌더링의 특징과 NEXT.js의 사용법을 배웠다.

아쉬운 점: 실무 위주의 설명보단 개념 위주의 정리인 내용이다.

향후 계획: 리액트 상태 관리 라이브러리의 필요성과 작동 방식을 배울 것이다.